﻿/*	
	stencil - action script 3 isometric game engine.

	http://code.google.com/p/stencilas3/
	
	Copyright (c) 2010 Max Zhuravkov, All Rights Reserved.

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
	of the Software, and to permit persons to whom the Software is furnished to do
	so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/
package stencil.core.engine {
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import stencil.core.engine.errors.UnsupportedComponentError;
	import stencil.core.engine.events.ComponentEvent;
	import stencil.core.engine.IComponent;
	import stencil.core.engine.errors.ComponentError;
	import stencil.core.errors.ArgumentIsNullError;
	import stencil.core.errors.UnsupportedMethodError;
	import stencil.core.engine.utils.getClassName;
	
	
	/**
	 * Implimentaion of IComponentList inrerface.
	 * 
	 * @author Max Zhuravkov
	 */
	public class ComponentList extends EventDispatcher implements IComponentList {
		
		private var _components:Array = [];
		
		private var _owner:ISceneObject;
		
		private var _enabled:Boolean = true;
		
		//----------------------------------------------------------------
		// Constructor
		//----------------------------------------------------------------
		
		public function ComponentList() {
			super();
		}
		
		//----------------------------------------------------------------
		// Public properties
		//----------------------------------------------------------------
		
		public final function set id(value:String):void {
			
		}
		
		/**
		 * @inheritDoc
		 */
		public final function get id():String {
			return this.owner.id;
		}
		
		/**
		 * @inheritDoc
		 */
		public function set owner(value:ISceneObject):void {
			if (this._owner == value) {
				return;
			}
			
			this._owner = value;
			this.ownerChanged(value);
		}
		
		public function get owner():ISceneObject {
			return this._owner;
		}
		
		/**
		 * The data property is not accessable in ComponentList. 
		 */
		public function set data(data:Object):void {
			throw new UnsupportedMethodError("set data");
		}
		
		public function get data():Object {
			throw new UnsupportedMethodError("get data");
			return null;
		}
		
		/**
		 * @inheritDoc
		 */
		public function set enabled(value:Boolean):void {
			if (this._enabled == value) {
				return;
			}
			this._enabled = value;
		}
		
		public function get enabled():Boolean {
			return this._enabled;
		}
		
		/**
		 * Serialization support.
		 * @private
		 */
		public function set components(value:Array):void {
			this._components = value;
			
			if (value) {
				for each(var component:IComponent in value) {
					this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_ADD_COMPONENT, component, true));
				}
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function get components():Array {
			return this._components;
		}
		
		//----------------------------------------------------------------
		// Public methods
		//----------------------------------------------------------------
		
		/**
		 * @inheritDoc
		 */
		public function addComponent(id:String, component:IComponent, data:Object = null):IComponent {
			if (!component) {
				throw new ArgumentIsNullError("component");
			}
			
			for each(var c:IComponent in this._components) {
				if (c == component) {
					throw new ComponentError(this.id, "component with identifier '" + id + "' is already exists." );
				}else {
					if (c.id == id) {
						throw new ComponentError(this.id, "component #" + id  + " of type '" + getClassName(component) + "'  is already added." );
					}
				}
			}
			
			if (this._components.indexOf(component) != -1) {
				throw new ComponentError(this.id, "Component (" + component + ") is already added.");
			}
			
			component['id'] = id;
			
			return this.addComponentToList(component, data);
		}
		
		/**
		 * @inheritDoc
		 */
		public function addComponentType(id:String, componentType:Class, data:Object = null):IComponent {
			if(!componentType){
				throw new ArgumentIsNullError("componentType");
			}
			
			var component:Object = new componentType();
			
			if (!(component is IComponent)) {
				throw new UnsupportedComponentError(this.id, componentType);
			}
			
			component['id'] = id;
			
			return this.addComponentToList(component as IComponent, data);
		}
		
		/**
		 * @inheritDoc
		 */
		public function getComponentById(id:String):IComponent {
			return this._components[this.getComponentIndexById(id, true)]
		}
		
		/**
		 * @inheritDoc
		 */
		public function getComponentsByType(componentType:Class):Array {
			if (!componentType) {
				throw new ArgumentIsNullError("componentType");
			}
			
			var components:Array = [];
			
			for each(var component:IComponent in this._components) {
				if (component['constructor'] == componentType) {
					components.push(component);
				}
			}
			
			if (!components.length) {
				throw new ComponentError(this.id, "No components of type '" + getClassName(componentType) + "' found.");
			}
			
			return components;
		}
		
		
		/**
		 * @inheritDoc
		 */
		public function getComponentByType(componentType:Class):IComponent {
			var components:Array = this.getComponentsByType(componentType);
			
			if (components.length > 1) {
				throw new ComponentError(this.id, "Found " + components.length + " components of type '" + getClassName(componentType) + "' but 1 is expected.");
			}
			
			return components[0];
		}
		
		/**
		 * @inheritDoc
		 */
		public function containsComponent(component:IComponent):Boolean {
			return this.getComponentIndex(component, false) != -1;
		}
		
		/**
		 * @inheritDoc
		 */
		public function containsComponentType(componentType:Class):Boolean {
			return this.getComponentIndexByType(componentType, false) != -1;
		}
		
		/**
		 * @inheritDoc
		 */
		public function removeComponent(component:IComponent):IComponent {
			var index:int = this.getComponentIndex(component, true);
			
			this._components.splice(index, 1);
			
			this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_REMOVE_COMPONENT, component, true));
			
			return component;
		}

		/**
		 * @inheritDoc
		 */
		public function removeComponentsByType(componentType:Class):Array {
			if (!componentType) {
				throw new ArgumentIsNullError("componentType");
			}
			
			if (!this._components.length) {
				return null;
			}
			var components:Array = [];
			var length:int = this._components.length;
			
			for (var i:int = 0; i < this._components.length; ) {
				var component:IComponent = this._components[i];
				
				if (component['constructor'] == componentType) {
					this._components.splice(i, 1);
				
					if (i > 0) {
						i --;
					}
					
					length --;
					
					components.push(component);
					
					this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_REMOVE_COMPONENT, component, true));
				}else {
					i ++;
				}
			}
			
			if (components.length == 0) {
				throw new ComponentError(this.id, "No components of type '" + getClassName(componentType) + " found.")
			}
			
			return components;
		}
		
		
		/**
		 * @inheritDoc
		 */
		public function removeComponentById(id:String):IComponent {
			var index:int = this.getComponentIndexById(id, true);
			var component:IComponent = this._components[index];
			
			this._components.splice(index, 1);
			
			this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_REMOVE_COMPONENT, component, true));
			
			return component;
		}
		
		/**
		 * @inheritDoc
		 */
		public function removeAllComponents():Array {
			if (!this._components.length) {
				return null;
			}
			var removedComponents:Array = new Array(this._components.length);
			var index:int = 0;	
			
			while (this._components.length > 0) {
				var component:IComponent = this._components.shift();
				removedComponents[index ++] = component;
				
				this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_REMOVE_COMPONENT, component, true));
			}
			
			return removedComponents;
		}
		
		/**
		 * @inheritDoc
		 */
		public function dispose():void {
			this.removeAllComponents();
			this.owner = null;
		}
		
		//----------------------------------------------------------------
		// Private methods
		//----------------------------------------------------------------
		
		private function addComponentToList(component:IComponent, data:Object):IComponent {
			component.owner = this.owner;
			component.data = data ? data : this.owner;
			
			this._components.push(component);
			
			this.dispatchEvent(new ComponentEvent(ComponentEvent.STN_ADD_COMPONENT, component, true));
			
			return component;
		}
		
		private function getComponentIndex(component:IComponent, required:Boolean):int {
			if (!component) {
				throw new ArgumentError("component");
			}
			var index:int = this._components.indexOf(component);
			
			if (required && index == -1) {
				throw new ComponentError(this.id, "Component #" + component.id + " of type '" + getClassName(component) + "' is not found." );
			}
			
			return index;
		}
		
		private function getComponentIndexByType(componentType:Class, required:Boolean):int {
			var length:int = this._components.length;
			for (var i:int = 0; i < length; i ++ ) {
				var component:IComponent = this._components[i];
				if (component['constructor'] == componentType) {
					return i;
				}
			}
			
			if (required) {
				var msg:String = "Component of type '" + getClassName(componentType) + "' is not found.";
				throw new ComponentError(this.id, msg);
			}
			
			return -1;
		}
		
		private function getComponentIndexById(id:String, required:Boolean):int {
			if (!id) {
				throw new ArgumentIsNullError("id");
			}
			var length:int = this.components.length;
			for (var i:int = 0; i < length; i ++ ) {
				var component:IComponent = this._components[i];
				if (component.id == id) {
					return i;
				}
			}
			
			if (required) {
				throw new ComponentError(this.id, "Component with identifier '" + id + "' is not found.");
			}
			
			return -1;
		}
		
		//------------------------------------------------------------------------------------------------
		// Protected methods
		//------------------------------------------------------------------------------------------------
		
		protected function ownerChanged(owner:ISceneObject):void {
			for each(var component:IComponent in this.components) {
				component.owner = owner;
				
				if (component.data == null) {
					component.data = owner;
				}
			}
		}
	}

}
